package com.treeman.user;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.UserDetails;

import com.treeman.friend.Invitation;
import com.treeman.marker.Marker;
import com.treeman.notification.Notification;

@Entity
@Table(name = "USER_DATA")
public class User implements UserDetails {

	private static final long serialVersionUID = -6298469979379811199L;

	/**
	 * Id użytkownika - PRIMARY_KEY
	 */
	@Id
	@Column(name = "USER_ID")
	@SequenceGenerator(name = "user_id_gen", sequenceName = "user_id_sequence", allocationSize = 1)
	@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_id_gen")
	private Long id;

	/**
	 * Login użytkownika.
	 */
	@Basic(optional = false)
	@Column(name = "USERNAME", unique = true, nullable = false)
	private String username;

	/**
	 * Hasło użytkownika. Po zapisie w bazie danych jest zahashowane
	 */
	@Basic(optional = false)
	@Column(name = "PASSWORD", nullable = false)
	private String password;

	/**
	 * Potwierdzenie hasła - walidacja, brak w bazie danych
	 */
	@Transient
	private String repeatedPassword;

	/**
	 * Adres email - wymagany, unikalny
	 */

	@Basic(optional = false)
	@Column(name = "EMAIL", unique = true, nullable = false)
	private String email;

	/**
	 * Data rejestracji
	 */
	@Basic(optional = false)
	@Column(name = "CREATED_DATE", nullable = false, updatable = false)
	@DateTimeFormat(iso = ISO.DATE_TIME)
	private Date createdDate = new Date();

	/**
	 * Data ostatniego logowania
	 */
	@Column(name = "LASTLOGIN_DATE")
	@DateTimeFormat(iso = ISO.DATE_TIME)
	private Date lastLoginDate;

	/**
	 * Data ostatniej aktywności
	 */
	@Transient
	private Date lastActive;

	/**
	 * Dodatkowe informacje o użytkowniku
	 */
	@Embedded
	private UserAdditionalInformations userAdditionalInformations;

	/**
	 * Role użytkownika. Możliwe role: {@link UserRole.Role}
	 */
	@OneToMany(cascade = CascadeType.ALL, mappedBy = "user", targetEntity = UserRole.class)
	@Fetch(FetchMode.JOIN)
	private Set<UserRole> userRoles = new HashSet<UserRole>();

	/**
	 * Markery użytkownika
	 */
	@OneToMany(cascade = CascadeType.ALL, mappedBy = "author", targetEntity = Marker.class)
	private List<Marker> markers = new ArrayList<Marker>();

	/**
	 * Znajomi użytkownika
	 */
	@ManyToMany(cascade = CascadeType.ALL)
	@JoinTable(name = "USER_FRIEND", joinColumns = { @JoinColumn(name = "USER_ID") }, inverseJoinColumns = { @JoinColumn(name = "FRIEND_ID") })
	private Set<User> friends = new HashSet<User>();

	/**
	 * Wysłane zaproszenia użytkownika
	 */
	@OneToMany(cascade = CascadeType.ALL, mappedBy = "from", targetEntity = Invitation.class)
	private Set<Invitation> sendedInvitations = new HashSet<Invitation>();

	/**
	 * Odebrane zaproszenia użytkownika
	 */
	@OneToMany(cascade = CascadeType.ALL, mappedBy = "to", targetEntity = Invitation.class)
	private Set<Invitation> recievedInvitations = new HashSet<Invitation>();

	/**
	 * Notifikacje użytkownika
	 * 
	 */
	@OneToMany(cascade = CascadeType.ALL, mappedBy = "to", targetEntity = Notification.class)
	private List<Notification> notifications = new ArrayList<Notification>();

	/*** Getters/Setters ***/

	public Long getId() {
		return id;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getRepeatedPassword() {
		return repeatedPassword;
	}

	public void setRepeatedPassword(String repeatedPassword) {
		this.repeatedPassword = repeatedPassword;
	}

	public Date getCreatedDate() {
		return createdDate;
	}

	public Date getLastLoginDate() {
		return lastLoginDate;
	}

	public void setLastLoginDate(Date lastLoginDate) {
		this.lastLoginDate = lastLoginDate;
	}

	public Set<UserRole> getUserRoles() {
		return userRoles;
	}

	public void setUserRoles(Set<UserRole> userRoles) {
		this.userRoles = userRoles;
	}

	public void addRole(UserRole userRole) {
		userRole.setUser(this);
		this.userRoles.add(userRole);
	}

	public List<Marker> getMarkers() {
		return markers;
	}

	public void setMarkers(List<Marker> markers) {
		this.markers = markers;
	}

	/************** Spring Security ****************/

	@Override
	public Collection<GrantedAuthority> getAuthorities() {

		Set<GrantedAuthority> ga = new HashSet<GrantedAuthority>();
		for (UserRole role : this.userRoles) {
			ga.add(new GrantedAuthorityImpl(role.getRole().toString()));
		}
		return ga;
	}

	@Override
	public boolean isAccountNonExpired() {
		return isEnabled();
	}

	@Override
	public boolean isAccountNonLocked() {
		return isEnabled();
	}

	@Override
	public boolean isCredentialsNonExpired() {
		return isEnabled();
	}

	@Override
	public boolean isEnabled() {
		if (this.userRoles.size() > 0)
			return true;
		else
			return false;
	}

	public Set<User> getFriends() {
		return friends;
	}

	public void setFriends(Set<User> friends) {
		this.friends = friends;
	}

	public Set<Invitation> getSendedInvitations() {
		return sendedInvitations;
	}

	public void setSendedInvitations(Set<Invitation> sendedInvitations) {
		this.sendedInvitations = sendedInvitations;
	}

	public Set<Invitation> getRecievedInvitations() {
		return recievedInvitations;
	}

	public void setRecievedInvitations(Set<Invitation> recievedInvitations) {
		this.recievedInvitations = recievedInvitations;
	}

	public List<Notification> getNotifications() {
		return notifications;
	}

	public void setNotifications(List<Notification> notifications) {
		this.notifications = notifications;
	}

	public Date getLastActive() {
		return lastActive;
	}

	public void setLastActive(Date lastActive) {
		this.lastActive = lastActive;
	}

	public UserAdditionalInformations getUserAdditionalInformations() {
		return userAdditionalInformations;
	}

	public void setUserAdditionalInformations(
			UserAdditionalInformations userAdditionalInformations) {
		this.userAdditionalInformations = userAdditionalInformations;
	}
}
